/***************************************************************************
 *   Copyleft 2008-2009 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "../vEngine3D.h"
#include "./vGLView.h"
#include "../API/vApis.h"
#include "../../Macros/vMacros.h"
#include "./vGlFrustum.h"


namespace Vendetta {
	vGlView::vGlView(): vView() {
		mpFrustum = new vGlFrustum;
	}

	vGlView::~vGlView() {
	}

	vVoid vGlView::SetFrustum(const vVector3D &leftBottomNear, const vVector3D &rightTopFar) {
		glFrustum(leftBottomNear[0], leftBottomNear[1], leftBottomNear[2], 
			rightTopFar[0], rightTopFar[1], rightTopFar[2]); V_GL_ASSERT
	}

	vVoid vGlView::SetPerspective(const vReal FOVY, const vReal aspect, const vReal newNear, const vReal newFar) {
		glMatrixMode(GL_PROJECTION); V_GL_ASSERT
		glLoadIdentity(); V_GL_ASSERT
		gluPerspective(
			FOVY,
			aspect,
			newNear,		// Can't use "near" and "far" names because they collide
			newFar);		// with standard C keywords GL_ASSERT
		glMatrixMode(GL_MODELVIEW); V_GL_ASSERT
	}

	vVoid vGlView::SetViewPort(const vInt &left, const vInt &bottom, const vInt &width, const vInt &height) {
		glViewport(left, bottom, width, height); V_GL_ASSERT
	}

	vVoid vGlView::SetDepthRange(const vReal newNear, const vReal newFar) {
		glDepthRange(newNear, newFar); V_GL_ASSERT
	}

	vVoid vGlView::SetOrtho(const vVector3D &leftBottomNear, const vVector3D &rightTopFar) {
		glOrtho(leftBottomNear[0], leftBottomNear[1], leftBottomNear[2], 
			rightTopFar[0], rightTopFar[1], rightTopFar[2]); V_GL_ASSERT
	}

	vVoid vGlView::LoadIdentity()
	{
		glLoadIdentity(); V_GL_ASSERT
	}

	vVoid vGlView::LoadTranslate(const vVector3D &vector) {
		glLoadIdentity(); V_GL_ASSERT
		glTranslatef(vector[0], vector[1], vector[2]); V_GL_ASSERT
	}

	vVoid vGlView::LoadTranslate(const vVector2D &vector) {
		glLoadIdentity(); V_GL_ASSERT
		glTranslatef(vector[0], vector[1], 0); V_GL_ASSERT
	}

	vVoid vGlView::LoadRotate(const vReal angle, const vVector3D &vector) {
		glLoadIdentity(); V_GL_ASSERT
		glRotatef(angle, vector[0], vector[1], vector[2]); V_GL_ASSERT
	}

	vVoid vGlView::LoadRotateZ(const vReal angle) {
		glRotatef(angle, 0, 0, 1); V_GL_ASSERT
	}

	vVoid vGlView::LoadScale(const vVector3D &vector) {
		glLoadIdentity(); V_GL_ASSERT
		glScalef(vector[0], vector[1], vector[2]); V_GL_ASSERT
	}

	vVoid vGlView::LoadMatrix(const vMatrix4D &matrix) {
		glLoadMatrixf((float*) &matrix); V_GL_ASSERT
	}

	vVoid vGlView::Translate(const vVector3D &vector) {
		glTranslatef(vector[0], vector[1], vector[2]); V_GL_ASSERT
	}

	vVoid vGlView::Translate(const vVector2D &vector) {
		glTranslatef(vector[0], vector[1], 0); V_GL_ASSERT
	}

	vVoid vGlView::Translate(const vReal x, const vReal y) {
		glTranslatef(x, y, 0); V_GL_ASSERT
	}

	vVoid vGlView::TranslateX(const vReal x) {
		glTranslatef(x, 0, 0); V_GL_ASSERT
	}

	vVoid vGlView::TranslateY(const vReal y) {
		glTranslatef(0, y, 0); V_GL_ASSERT
	}

	vVoid vGlView::Rotate(const vReal angle, const vVector3D &vector) {
		glRotatef(angle, vector[0], vector[1], vector[2]); V_GL_ASSERT
	}

	vVoid vGlView::RotateZ(const vReal angle) {
		glRotatef(angle, 0, 0, 1); V_GL_ASSERT
	}

	vVoid vGlView::RotateY(const vReal angle) {
		glRotatef(angle, 0, 1, 0); V_GL_ASSERT
	}

	vVoid vGlView::RotateX(const vReal angle) {
		glRotatef(angle, 1, 0, 0); V_GL_ASSERT
	}

	vVoid vGlView::Scale(const vVector3D &vector) {
		glScalef(vector[0], vector[1], vector[2]); V_GL_ASSERT
	}

	vVoid vGlView::MultMatrix(const vMatrix4D &matrix) {
		glMultMatrixf((float*) &matrix); V_GL_ASSERT
	}

	vVoid vGlView::LookAt(const vVector3D &eyeVertex, const vVector3D &tarVertex, const vVector3D &upVector) {
		gluLookAt(
			eyeVertex[0], eyeVertex[1], eyeVertex[2],
			tarVertex[0], tarVertex[1], tarVertex[2],
			upVector[0], upVector[1], upVector[2]); V_GL_ASSERT
	}

	vVoid vGlView::PushMatrix() {
		glPushMatrix(); V_GL_ASSERT
	}

	vVoid vGlView::PopMatrix() {
		glPopMatrix(); V_GL_ASSERT
	}

	vVoid vGlView::EnableOrtho() {
		glMatrixMode(GL_PROJECTION); V_GL_ASSERT
		this->PushMatrix();
		this->LoadIdentity();
		this->SetOrtho(vVector3D(0, (vReal) vEngine3D::InstancePtr()->MainWindowPtr()->Width(), 0), 
			vVector3D((vReal) vEngine3D::InstancePtr()->MainWindowPtr()->Height(), -1, 1));
		this->Translate(vVector2D(0, (vReal) vEngine3D::InstancePtr()->MainWindowPtr()->Height()));
		this->Scale(vVector3D(1, -1, 1));
		
		glMatrixMode(GL_MODELVIEW); V_GL_ASSERT
		this->LoadIdentity();

		vEngine3D::InstancePtr()->StatePtr()->SetDepthTest(vState::OFF);
		vEngine3D::InstancePtr()->StatePtr()->SetDepthMask(vState::OFF);
	}

	vVoid vGlView::DisableOrtho() {
		glMatrixMode(GL_PROJECTION); V_GL_ASSERT
		this->PopMatrix();
		
		glMatrixMode(GL_MODELVIEW); V_GL_ASSERT
		vEngine3D::InstancePtr()->StatePtr()->SetDepthTest(vState::ON);
		vEngine3D::InstancePtr()->StatePtr()->SetDepthMask(vState::ON);
	}

	vVoid vGlView::EnableScissors(const vRectangle& rectangle) {
		glEnable(GL_SCISSOR_TEST); V_GL_ASSERT
		if (rectangle.Width() >= 0 && rectangle.Height() >= 0) {
			glScissor(
				(vInt) rectangle.Left(),
				(vInt) (vEngine3D::InstancePtr()->MainWindowPtr()->Height() - rectangle.Bottom() - 1),
				(vInt) rectangle.Width(),
				(vInt) rectangle.Height()); V_GL_ASSERT
		} else {
			glScissor(
				(vInt) rectangle.Left(),
				(vInt) (vEngine3D::InstancePtr()->MainWindowPtr()->Height() - rectangle.Bottom() - 1),
				0,
				0); V_GL_ASSERT
		}
	}

	vVoid vGlView::DisableScissors() {
		glDisable(GL_SCISSOR_TEST); V_GL_ASSERT
	}

	vVoid vGlView::PushScissors(const vRectangle& rectangle) {
		vRectangle intersection;
		
		// Compute intersection of current scissor rectangle and new one
		if (!mScissorsStack.IsEmpty()) {
			intersection.SetLeft(Max(mScissorsStack.Top().Left(), rectangle.Left()));
			intersection.SetRight(Min(mScissorsStack.Top().Right(), rectangle.Right()));
			intersection.SetTop(Max(mScissorsStack.Top().Top(), rectangle.Top()));
			intersection.SetBottom(Min(mScissorsStack.Top().Bottom(), rectangle.Bottom()));
		} else {
			intersection = rectangle;
		}

		EnableScissors(intersection);
		mScissorsStack.Push(intersection);
	}

	vVoid vGlView::PopScissors() {
		if (mScissorsStack.IsEmpty()) {
			throw vException("vGLView::PopScissors(): Scissors stack is empty !");
		}
		mScissorsStack.Pop();
		if (mScissorsStack.IsEmpty()) {
			DisableScissors();
		} else {
			EnableScissors(mScissorsStack.Top());
		}
	}

	vVoid vGlView::Project(const vVector3D &vertex, vVector2D &screenCoords) {
		// TODO
		throw vException("vGLView::Project() not yet implemented !");
	}

	vVoid vGlView::Unproject(vVector3D &vertex, const vVector2D &screenCoords) {
		// TODO
		throw vException("vGLView::Unproject() not yet implemented !");
	}

	vVector3D vGlView::Origin() const {
		// TODO
		throw vException("vGLView::Origin() not yet implemented !");
		return vVector3D();
	}

	vVector3D vGlView::Angles() const {
		// TODO
		throw vException("vGLView::Angles() not yet implemented !");
		return vVector3D();
	}

	vVector2D vGlView::Translation() const {
		vMatrix4D matrix;

		glGetFloatv(GL_MODELVIEW_MATRIX, (vReal*) &matrix); V_GL_ASSERT

		return vVector2D(matrix[3][0], matrix[3][1]);
	}

	vVoid vGlView::ModelViewMatrix(vMatrix4D &matrix) const	{
		glGetFloatv(GL_MODELVIEW_MATRIX, (vReal*) &matrix); V_GL_ASSERT
	}

	vVoid vGlView::ProjectionMatrix(vMatrix4D &matrix) const {
		glGetFloatv(GL_PROJECTION_MATRIX, (vReal*) &matrix); V_GL_ASSERT
	}
}